/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include "dal/allocator.h"
#include "dal/config_loader.h"
#include "dal_fastdds/converter/StatusConverter.hpp"
#include "dal_fastdds/converter/StatusMaskConverter.hpp"
#include "dal_fastdds/converter/QosConverter.hpp"
#include "dal_fastdds/converter/RetTypeConverter.hpp"
#include "dal_fastdds/converter/GuidConverter.hpp"
#include "dal_fastdds/listener_impl/DomainParticipantListenerImpl.hpp"
#include "dal_fastdds/utils/log.hpp"
#include "dal_fastdds/utils/EntityGetter.hpp"
#include "dal_fastdds/utils/EntityMappingManager.hpp"
#include "dal_fastdds/config_manager/ConfigManager.hpp"
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>

using namespace eprosima::fastdds::dds;

// PARTICIPANT
dal_domain_participant_listener_t*
dal_create_domain_participant_listener(dal_domain_participant_listener_callbacks_t callbacks)
{
    dal_domain_participant_listener_t* listener = dal_domain_participant_listener_allocate();
    if (listener == nullptr) {
        return nullptr;
    }
    listener->handle_ = (new DomainParticipantListenerImpl(callbacks));
    if (!listener->handle_) {
        LOG_ERROR() << "DomainParticipantListenerImpl memory allocation failed." << LOG_ENDL;
        dal_domain_participant_listener_free(listener);
        return nullptr;
    }

    return listener;
}

dal_ret_t dal_delete_domain_participant_listener(dal_domain_participant_listener_t* listener)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    delete reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_);
    dal_domain_participant_listener_free(listener);
    reinterpret_cast<dal_domain_participant_listener_t*>(listener)->handle_ = nullptr;

    return DAL_RETCODE_OK;
}

dal_domain_participant_t* dal_create_domain_participant(dal_domain_id domain_id,
                                                        dal_domain_participant_qos_t* qos,
                                                        dal_domain_participant_listener_t* listener,
                                                        dal_status_mask_t mask)
{
    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (domain_id < 0 || domain_id > DAL_DOMAIN_ID_MAX) {
        LOG_ERROR_WITH_LOCATION()
            << "domain_id is invalid, a number between 0 and 160 is recommended" << LOG_ENDL;
        return nullptr;
    }

    dal_domain_participant_t* participant = dal_domain_participant_allocate();
    if (!participant) {
        LOG_ERROR_WITH_LOCATION() << "dal domain_participant allocate failed." << LOG_ENDL;
        return nullptr;
    }

    DomainParticipantQos f_qos;
    d_ret = QosConverter::GetInstance()->ConvertParticipantQosFromDalToFastdds(qos, &f_qos);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert domain participant qos failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    StatusMask f_mask;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToFastdds(mask, &f_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert domain participant statusmask failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    DomainParticipantListenerImpl* f_listener =
        listener ? reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_) : nullptr;

    DomainParticipant* f_participant = DomainParticipantFactory::get_instance()->create_participant(
        domain_id, f_qos, f_listener, f_mask);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "create domain_participant failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    EntityMappingAddDomainParticipant(f_participant, participant);

    participant->handle_ = new DalDomainParticipantImpl(f_participant, f_listener);
    return participant;
}

dal_domain_participant_t*
dal_create_domain_participant_with_profile(dal_domain_id domain_id,
                                           dal_domain_participant_qos_t* qos,
                                           const char* profile_name,
                                           dal_domain_participant_listener_t* listener,
                                           dal_status_mask_t mask)
{
    if (!profile_name || strlen(profile_name) == 0) {
        return dal_create_domain_participant(domain_id, qos, listener, mask);
    }

    dal_ret_t d_ret = DAL_RETCODE_OK;

    if (domain_id < 0 || domain_id > DAL_DOMAIN_ID_MAX) {
        LOG_ERROR_WITH_LOCATION()
            << "domain_id is invalid, a number between 0 and 160 is recommended" << LOG_ENDL;
        return nullptr;
    }

    dal_domain_participant_t* participant = dal_domain_participant_allocate();
    if (!participant) {
        LOG_ERROR_WITH_LOCATION() << "dal domain_participant allocate failed." << LOG_ENDL;
        return nullptr;
    }

    // if user doesn't call api to load config file, we will try to load config file by env.
    if (ConfigManager::GetInstance()->getConfigType() != ConfigManager::CONFIG_TYPE_SET_BY_API &&
        ConfigManager::GetInstance()->getConfigType() != ConfigManager::CONFIG_TYPE_SET_BY_ENV) {
        ConfigManager::GetInstance()->loadConfigProfileByEnv();
    }

    DomainParticipantQos f_qos;
    if (!ConfigManager::GetInstance()->getFastddsProfilePath().empty()) {
        try {
            DomainParticipantFactory::get_instance()->get_participant_qos_from_profile(profile_name,
                                                                                       f_qos);
        }
        catch (...) {
            LOG_ERROR_WITH_LOCATION()
                << "participant qos profile[" << profile_name << "] in xml are not effective.";
            dal_domain_participant_free(participant);
            return nullptr;
        }

        const char* env_enable = getenv("CYBER_DAL_ENABLE");
        if (env_enable && strcmp(env_enable, "true") == 0) {
            d_ret = QosConverter::GetInstance()->ConvertParticipantQosFromDalToFastdds(qos, &f_qos);
            if (DAL_RETCODE_OK != d_ret) {
                LOG_ERROR_WITH_LOCATION() << "convert domain participant qos failed." << LOG_ENDL;
                dal_domain_participant_free(participant);
                return nullptr;
            }
        }
    } else {
        d_ret = QosConverter::GetInstance()->ConvertParticipantQosFromDalToFastdds(qos, &f_qos);
        if (DAL_RETCODE_OK != d_ret) {
            LOG_ERROR_WITH_LOCATION() << "convert domain participant qos failed." << LOG_ENDL;
            dal_domain_participant_free(participant);
            return nullptr;
        }
    }

    StatusMask f_mask;
    d_ret = StatusMaskConverter::GetInstance()->ConvertStatusMaskFromDalToFastdds(mask, &f_mask);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "convert domain participant statusmask failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    DomainParticipantListenerImpl* f_listener =
        listener ? reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_) : nullptr;

    DomainParticipant* f_participant = DomainParticipantFactory::get_instance()->create_participant(
        domain_id, f_qos, f_listener, f_mask);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "create domain_participant failed." << LOG_ENDL;
        dal_domain_participant_free(participant);
        return nullptr;
    }

    EntityMappingAddDomainParticipant(f_participant, participant);

    participant->handle_ = new DalDomainParticipantImpl(f_participant, f_listener);
    return participant;
}

dal_ret_t dal_delete_domain_participant(dal_domain_participant_t* participant)
{
    DalDomainParticipantImpl* participant_impl =
        reinterpret_cast<DalDomainParticipantImpl*>(participant->handle_);
    if (!participant_impl->Clear()) {
        LOG_ERROR_WITH_LOCATION() << "clear domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_ERROR;
    }

    DomainParticipant* f_participant = get_fastdds_domain_participant(participant);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    ReturnCode_t f_ret =
        DomainParticipantFactory::get_instance()->delete_participant(f_participant);

    dal_ret_t d_ret = RetTypeConverter::GetInstance()->ConvertRetFromFastddsToDal(f_ret);
    if (DAL_RETCODE_OK != d_ret) {
        LOG_ERROR_WITH_LOCATION() << "delete domain participant failed." << d_ret << LOG_ENDL;
        return d_ret;
    }

    EntityMappingRemoveDomainParticipant(f_participant);

    delete participant_impl;

    dal_domain_participant_free(participant);
    reinterpret_cast<dal_domain_participant_t*>(participant)->handle_ = nullptr;
    return DAL_RETCODE_OK;
}

dal_guid_t dal_domain_participant_get_guid(dal_domain_participant_t* participant)
{
    dal_guid_t d_guid = {};
    DomainParticipant* f_participant = get_fastdds_domain_participant(participant);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds domain participant failed." << LOG_ENDL;
        return d_guid;
    }

    auto f_guid = f_participant->guid();
    GuidConverter::GetInstance()->ConvertGuidFromFastddsToDal(&f_guid, &d_guid);
    return d_guid;
}

dal_ret_t dal_domain_participant_listener_set_participant_monitor_callback(
    dal_domain_participant_listener_t* listener,
    dal_participant_monitor_callback callback,
    void* callback_data)
{
    if (!listener || !listener->handle_) {
        LOG_ERROR_WITH_LOCATION() << "listener is invalid." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }
    auto listener_impl = reinterpret_cast<DomainParticipantListenerImpl*>(listener->handle_);
    listener_impl->set_participant_monitor_callback(callback);
    listener_impl->set_participant_monitor_callback_data(callback_data);
    return DAL_RETCODE_OK;
}

dal_ret_t dal_domain_participant_delete_contained_entities(dal_domain_participant_t* participant)
{
    DomainParticipant* f_participant = get_fastdds_domain_participant(participant);
    if (!f_participant) {
        LOG_ERROR_WITH_LOCATION() << "get fastdds domain participant failed." << LOG_ENDL;
        return DAL_RETCODE_BAD_PARAMETER;
    }

    ReturnCode_t f_ret = f_participant->delete_contained_entities();
    return RetTypeConverter::GetInstance()->ConvertRetFromFastddsToDal(f_ret);
}
